AWS Step Functions LocalによるMockテストをJestで実行してみた

AWS Step Functions LocalによるMockテストをJestで実行してみた

Clock Icon2022.07.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

前回のエントリでは、AWS Step Function LocalをMock Responseを使って試してみました。

AWS Step Function Localによるテストは便利なのですが、せっかくならテスト結果の評価まで含めて自動化したいですね。

そこで今回は、AWS Step Functions LocalによるMockテストをJestで実行してみました。

やってみた

AWS Step Function Local環境の準備

テスト対象のState Machine(後述)に対するMock ResponseのConfigです。これによりAWS ServiceからTaskへのレスポンスをMockすることができます。またレスポンスは複数のパターンを記述し、テスト実行時に使用することが可能です。

{
  "StateMachines": {
    "MyStateMachine": {
      "TestCases": {
        "FugaPathTest": {
          "GetParameter": "GetParameterFugaMockedSuccess"
        },
        "NotFugaPathTest": {
          "GetParameter": "GetParameterNotFugaMockedSuccess"
        }
      }
    }
  },
  "MockedResponses": {
    "GetParameterFugaMockedSuccess": {
      "0": {
        "Return": {
          "Parameter": {
            "Arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/hoge",
            "DataType": "text",
            "LastModifiedDate": "2022-07-26T05:38:43.052Z",
            "Name": "hoge",
            "Type": "String",
            "Value": "fuga",
            "Version": 4
          }
        }
      }
    },
    "GetParameterNotFugaMockedSuccess": {
      "0": {
        "Return": {
          "Parameter": {
            "Arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/hoge",
            "DataType": "text",
            "LastModifiedDate": "2022-07-26T05:38:43.052Z",
            "Name": "hoge",
            "Type": "String",
            "Value": "nyao",
            "Version": 4
          }
        }
      }
    }
  }
}

AWS Step Function LocalのDockerイメージamazon/aws-stepfunctions-localをPullします。

docker pull amazon/aws-stepfunctions-local

Pullしたイメージからコンテナを実行します。

docker run -p 8083:8083 \
  --mount type=bind,readonly,source=$(pwd)/MockConfigFile.json,destination=/home/StepFunctionsLocal/MockConfigFile.json \
  -e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/MockConfigFile.json" \
  amazon/aws-stepfunctions-local

Jestによるテスト実行

テスト対象となるState MachineのASL(Amazon States Language)定義のファイルです。

{
  "Comment": "A description of my state machine",
  "StartAt": "GetParameter",
  "States": {
    "GetParameter": {
      "Type": "Task",
      "Next": "Choice",
      "Parameters": {
        "Name": "hoge"
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getParameter",
      "ResultSelector": {
        "hoge.$": "$.Parameter.Value"
      }
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.hoge",
          "StringMatches": "fuga",
          "Next": "FugaPath"
        }
      ],
      "Default": "NotFugaPath"
    },
    "FugaPath": {
      "Type": "Pass",
      "End": true
    },
    "NotFugaPath": {
      "Type": "Pass",
      "End": true
    }
  }
}

AWS Systems Manager Parameter StoreからGet:Parameterで値を取得し、値の文字列によりChoice Stateでパスの分岐を行います。

Jestのテストコードです。上述のASLファイルを使用してコンテナ上にState Machieを作成して実行し、実行が成功していることおよびMock Responseで定義したテストケース毎に想定したパスを経由していることを確認しています。

import {
  SFNClient,
  GetExecutionHistoryCommand,
  StartExecutionCommand,
  CreateStateMachineCommand,
  DescribeExecutionCommand,
  ExecutionStatus,
  HistoryEvent,
  DeleteStateMachineCommand,
} from '@aws-sdk/client-sfn';
import TEST_TARGET_ASL = require('./MyStateMachine.asl.json');

const DUMMY_AWS_REGION = 'us-east-1';
const DUMMY_AWS_ACCOUNT = '123456789012';
const STEP_FUNCTIONS_LOCAL_ENDPOINT = 'http://localhost:8083';
const DUMMY_EXECUTION_ROLE_ARN = `arn:aws:iam::${DUMMY_AWS_ACCOUNT}:role/DummyRole`;
const STATE_MACHINE_NAME = 'MyStateMachine';
const DUMMY_STATE_MACHINE_ARN = `arn:aws:states:${DUMMY_AWS_REGION}:${DUMMY_AWS_ACCOUNT}:stateMachine:${STATE_MACHINE_NAME}`;

const sfnClient = new SFNClient({
  region: DUMMY_AWS_REGION,
  endpoint: STEP_FUNCTIONS_LOCAL_ENDPOINT, //AWS Step Functions Localのエンドポイントを指定
});

//State Machineの作成
beforeAll(async () => {
  await sfnClient.send(
    new CreateStateMachineCommand({
      name: STATE_MACHINE_NAME,
      roleArn: DUMMY_EXECUTION_ROLE_ARN,
      definition: JSON.stringify(TEST_TARGET_ASL),
    }),
  );
});

//State Machineの削除
afterAll(async () => {
  await sfnClient.send(
    new DeleteStateMachineCommand({
      stateMachineArn: DUMMY_STATE_MACHINE_ARN,
    }),
  );
});

//State Machine実行の開始および履歴取得
const startExecutionAndGetExecutionHistory = async (
  testCase: string,
): Promise<HistoryEvent[]> => {
  const executionResult = await sfnClient.send(
    new StartExecutionCommand({
      name: testCase,
      stateMachineArn: `${DUMMY_STATE_MACHINE_ARN}#${testCase}`,
    }),
  );
  const executionArn = executionResult.executionArn;

  let executionStatus = 'RUNNING';
  while (executionStatus === 'RUNNING') {
    await new Promise((r) => setTimeout(r, 1000));

    const res = await sfnClient.send(
      new DescribeExecutionCommand({
        executionArn: executionArn,
      }),
    );

    if (res.status !== 'RUNNING')
      executionStatus = res.status as ExecutionStatus;
  }

  const executionHistory = await sfnClient.send(
    new GetExecutionHistoryCommand({
      executionArn: executionArn,
      includeExecutionData: true,
    }),
  );

  return executionHistory.events as HistoryEvent[];
};

describe('MyStateMachine', () => {
  test('FugaPathTest', async () => {
    const executionHistory = await startExecutionAndGetExecutionHistory(
      'FugaPathTest',
    );

    //State Machine実行の分岐がFugaPathを経由していることを確認
    expect(executionHistory[8].stateEnteredEventDetails?.name).toBe('FugaPath');
    //State Machine実行が成功していることを確認
    expect(executionHistory[10].type).toBe('ExecutionSucceeded');
  });

  test('NotFugaPathTest', async () => {
    const executionHistory = await startExecutionAndGetExecutionHistory(
      'NotFugaPathTest',
    );

    //State Machine実行の分岐がNotFugaPathを経由していることを確認
    expect(executionHistory[8].stateEnteredEventDetails?.name).toBe(
      'NotFugaPath',
    );
    //State Machine実行が成功していることを確認
    expect(executionHistory[10].type).toBe('ExecutionSucceeded');
  });
});

ASLをJsonファイルからImportするので、tsconfig.jsoncompilerOptions.resolveJsonModuletrueに設定します。

{
  "compilerOptions": {
    "resolveJsonModule": true
  }
}

Jestでテストを実行すると、Successしました。

$ npx jest MyStateMachine.test.ts
 PASS  ./MyStateMachine.test.ts (5.376 s)
  MyStateMachine
    ✓ FugaPathTest (1054 ms)
    ✓ NotFugaPathTest (1043 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.413 s
Ran all test suites matching /MyStateMachine.test.ts/i.

おわりに

AWS Step Functions LocalによるMockテストをJestで実行してみました。

ドキュメントで紹介されているテスト方法はAWS CLIを使う方法のみでしたが、AWS SDKからもAWS Step Functions Localを使用できてよかったです。

参考

以上

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.